// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "planetaryindexbuffermanager.h"
#include "planetaryscenemanager.h"
#include "planetaryoptions.h"
#include <Ogre.h>

namespace Ogre
{
	PlanetaryIndexBufferManager::PlanetaryIndexBufferManager(PlanetarySceneManager *mgr)
	{
		mSceneMgr = mgr;
		mOptions = mSceneMgr->getOptions();
	}

	PlanetaryIndexBufferManager::~PlanetaryIndexBufferManager()
	{
		clear();
	}

	void PlanetaryIndexBufferManager::clear()
	{
		while(!mIndices.empty())
		{
			mIndices[mIndices.size() - 1]->buffer.setNull();
			delete mIndices[mIndices.size() - 1];
			mIndices.pop_back();
		}
	}

	/// Get index buffer for the requested type, only one implemented for now
	HardwareIndexBufferSharedPtr PlanetaryIndexBufferManager::getIndexBuffer(unsigned int type, unsigned int LOD)
	{
		// Check if we've already loaded one
		std::vector<INDEXBUFFERINFO *>::iterator itr;
		for(itr = mIndices.begin(); itr != mIndices.end(); itr++)
		{
			if((*itr)->type == type && (*itr)->LOD == LOD)
				return (*itr)->buffer;
		}

		// Create new index buffer of this type
		INDEXBUFFERINFO *info;


		switch(type)
		{
		case INDEX_BUFFER_TILE: // Renderable
			info = createTileIndexBuffer(LOD);
			mIndices.push_back(info);
			return info->buffer;

		case INDEX_BUFFER_PAGE: // PageRenderable
			info = createPageIndexBuffer(LOD);
			mIndices.push_back(info);
			return info->buffer;

		case INDEX_BUFFER_ZONE:
			info = createZoneIndexBuffer(LOD);
			mIndices.push_back(info);
			return info->buffer;

		case INDEX_BUFFER_ATMO:
			info = createAtmoIndexBuffer(LOD);
			mIndices.push_back(info);
			return info->buffer;

		case INDEX_BUFFER_ZONE_TO_PAGE_JOINT:
			info = createZoneToPageJoint();
			mIndices.push_back(info);
			return info->buffer;

		case INDEX_BUFFER_PAGE_TO_TILE_JOINT:
			info = createPageToTileJoint();
			mIndices.push_back(info);
			return info->buffer;
		}

		throw new Ogre::Exception(-1,String("Invalid index buffer type"),String(__FILE__) + StringConverter::toString(__LINE__) + String(": PlanetaryIndexBufferManager::getIndexBuffer(int type)") );
	}

	int *PlanetaryIndexBufferManager::getIndexShadowBuffer(unsigned int type, unsigned int LOD)
	{
		// Check if we've already loaded one
		std::vector<INDEXBUFFERINFO *>::iterator itr;
		for(itr = mIndices.begin(); itr != mIndices.end(); itr++)
		{
			if((*itr)->type == type && (*itr)->LOD == LOD)
				return (*itr)->MemoryData;
		}

		throw new Ogre::Exception(-1,String("Index Shadow Buffer not found"),String(__FILE__) + StringConverter::toString(__LINE__) + String(": PlanetaryIndexBufferManager::getIndexBuffer(int type)") );
	}

	unsigned int PlanetaryIndexBufferManager::getIndexBufferSize(unsigned int type, unsigned int LOD)
	{
		switch(type)
		{
		case INDEX_BUFFER_TILE:
			switch(LOD)
			{
			case 0:
				return 2 * 3 * (mOptions->TileSize - 1) * (mOptions->TileSize - 1);

			default:
				throw new Ogre::Exception(-1,String("Invalid index buffer type"),String(__FILE__) + StringConverter::toString(__LINE__) + String(": PlanetaryIndexBufferManager::getIndexBuffer(int type)") );
			}

		case INDEX_BUFFER_PAGE:
			switch(LOD)
			{
			case 0:
				return 2 * 3 * (mOptions->mPageSizeLOD1 - 1) * (mOptions->mPageSizeLOD1 - 1);
			default:
				throw new Ogre::Exception(-1,String("Invalid index buffer type"),String(__FILE__) + StringConverter::toString(__LINE__) + String(": PlanetaryIndexBufferManager::getIndexBuffer(int type)") );
			}

		case INDEX_BUFFER_ZONE:
			switch(LOD)
			{
			case 0:
				return 2 * 3 * (mOptions->mZoneSizeLOD1 - 1) * (mOptions->mZoneSizeLOD1 - 1);
			default:
				throw new Ogre::Exception(-1,String("Invalid index buffer type"),String(__FILE__) + StringConverter::toString(__LINE__) + String(": PlanetaryIndexBufferManager::getIndexBuffer(int type)") );
			}
		case INDEX_BUFFER_ATMO:
			switch(LOD)
			{
			case 0:
				return 2 * 3 * (mOptions->mAtmoSizeLOD0 - 1) * (mOptions->mAtmoSizeLOD0 - 1);
			case 1:
				return 2 * 3 * (mOptions->mAtmoSizeLOD1 - 1) * (mOptions->mAtmoSizeLOD1 - 1);
			case 2:
				return 2 * 3 * (mOptions->mAtmoSizeLOD2 - 1) * (mOptions->mAtmoSizeLOD2 - 1);

			default:
				throw new Ogre::Exception(-1,String("Invalid index buffer type"),String(__FILE__) + StringConverter::toString(__LINE__) + String(": PlanetaryIndexBufferManager::getIndexBuffer(int type)") );
			}

		case INDEX_BUFFER_ZONE_TO_PAGE_JOINT:
			{
				const unsigned int rowlength = mOptions->mZoneSizeLOD1;
				const unsigned int rowlength2 = (mOptions->mPageSizeLOD1 - 1) * mOptions->ZoneSize + 1;
				unsigned int trianglesPrZoneVert = Math::Ceil((rowlength2-1) / (rowlength-1));
				
				return 3 * trianglesPrZoneVert * (mOptions->mZoneSizeLOD1 - 1) + 3 * (mOptions->mZoneSizeLOD1 - 1);
			}

		case INDEX_BUFFER_PAGE_TO_TILE_JOINT:
			{
				const unsigned int rowlength = mOptions->mPageSizeLOD1;
				const unsigned int rowlength2 = (mOptions->TileSize - 1) * mOptions->PageSize + 1;
				unsigned int trianglesPrZoneVert = Math::Ceil((rowlength2-1) / (rowlength-1));
				
				return 3 * trianglesPrZoneVert * (mOptions->mPageSizeLOD1 - 1) + 3 * (mOptions->mPageSizeLOD1 - 1);
			}
		}

		throw new Ogre::Exception(-1,String("Invalid index buffer type"),String(__FILE__) + StringConverter::toString(__LINE__) + String(": PlanetaryIndexBufferManager::getIndexBuffer(int type)") );
	}

	PlanetaryIndexBufferManager::INDEXBUFFERINFO *PlanetaryIndexBufferManager::createTileIndexBuffer(unsigned int LOD)
	{
		uint16 *idata;
		INDEXBUFFERINFO *info = new INDEXBUFFERINFO;

		info->type = INDEX_BUFFER_TILE;
		info->LOD = LOD;
		info->buffer = HardwareBufferManager::getSingleton().createIndexBuffer(
							HardwareIndexBuffer::IT_16BIT,
							getIndexBufferSize(info->type),
							HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		idata = (uint16*) info->buffer->lock(HardwareBuffer::HBL_DISCARD);

		int *memData = new int[getIndexBufferSize(info->type)];

		int *p = memData;


		for(unsigned int i = 0; i < (mOptions->TileSize - 1); i++)
		{
			for(unsigned int j = 0; j < (mOptions->TileSize - 1); j++)
			{
				// Face 1 of the quad
				*idata++ = *p++ = i * (mOptions->TileSize) + j;
				*idata++ = *p++ = (i+1) * (mOptions->TileSize) + j;
				*idata++ = *p++ = i * (mOptions->TileSize) + (j + 1);

				// Face 2 of the quad
				*idata++ = *p++ = i * (mOptions->TileSize) + (j + 1);
				*idata++ = *p++ = (i+1) * (mOptions->TileSize) + j;
				*idata++ = *p++ = (i+1) * (mOptions->TileSize) + (j+1);
			}
		}

		info->buffer->unlock();

		info->MemoryData = memData;

		return info;
	}
	
	PlanetaryIndexBufferManager::INDEXBUFFERINFO *PlanetaryIndexBufferManager::createPageIndexBuffer(unsigned int LOD)
	{
		uint16 *idata;
		INDEXBUFFERINFO *info = new INDEXBUFFERINFO;

		info->type = INDEX_BUFFER_PAGE;
		info->LOD = LOD;
		info->buffer = HardwareBufferManager::getSingleton().createIndexBuffer(
				HardwareIndexBuffer::IT_16BIT,
				getIndexBufferSize(info->type),
				HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		idata = (uint16*) info->buffer->lock(HardwareBuffer::HBL_DISCARD);

		for(unsigned int i = 0; i < mOptions->mPageSizeLOD1 - 1; i++)
		{
			for(unsigned int j = 0; j < mOptions->mPageSizeLOD1 - 1; j++)
			{
				// Face 1 of the quad
				*idata++ = i * mOptions->mPageSizeLOD1 + j;
				*idata++ = (i+1) * mOptions->mPageSizeLOD1 + j;
				*idata++ = i * mOptions->mPageSizeLOD1 + (j + 1);
				
				// Face 2 of the quad
				*idata++ = i * mOptions->mPageSizeLOD1 + (j + 1);
				*idata++ = (i+1) * mOptions->mPageSizeLOD1 + j;
				*idata++ = (i+1) * mOptions->mPageSizeLOD1 + (j+1);
			}
		}

		info->buffer->unlock();
		return info;
	}
	
	PlanetaryIndexBufferManager::INDEXBUFFERINFO *PlanetaryIndexBufferManager::createZoneIndexBuffer(unsigned int LOD)
	{
		uint16 *idata;
		INDEXBUFFERINFO *info = new INDEXBUFFERINFO;

		info->type = INDEX_BUFFER_ZONE;
		info->LOD = LOD;
		info->buffer = HardwareBufferManager::getSingleton().createIndexBuffer(
			HardwareIndexBuffer::IT_16BIT,
			getIndexBufferSize(info->type),
			HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		idata = (uint16*)info->buffer->lock(HardwareBuffer::HBL_DISCARD);

		for(unsigned int i = 0; i < mOptions->mZoneSizeLOD1 - 1; i++)
		{
			for(unsigned int j = 0; j < mOptions->mZoneSizeLOD1 - 1; j++)
			{
				// Face 1 of the quad
				*idata++ = i * mOptions->mZoneSizeLOD1 + j;
				*idata++ = (i+1) * mOptions->mZoneSizeLOD1 + j;
				*idata++ = i * mOptions->mZoneSizeLOD1 + (j + 1);
				
				// Face 2 of the quad
				*idata++ = i * mOptions->mZoneSizeLOD1 + (j + 1);
				*idata++ = (i+1) * mOptions->mZoneSizeLOD1 + j;
				*idata++ = (i+1) * mOptions->mZoneSizeLOD1 + (j+1);
			}
		}

		info->buffer->unlock();
		return info;
	}
	
	PlanetaryIndexBufferManager::INDEXBUFFERINFO *PlanetaryIndexBufferManager::createAtmoIndexBuffer(unsigned int LOD)
	{
		uint16 *idata;
		INDEXBUFFERINFO *info = new INDEXBUFFERINFO;
		unsigned int rowlength;

		switch(LOD)
		{
		case 0:
			rowlength = mOptions->mAtmoSizeLOD0;
			break;
		case 1:
			rowlength = mOptions->mAtmoSizeLOD1;
			break;
		case 2:
			rowlength = mOptions->mAtmoSizeLOD2;
			break;
		}

		info->type = INDEX_BUFFER_ATMO;
		info->LOD = LOD;
		info->buffer = HardwareBufferManager::getSingleton().createIndexBuffer(
			HardwareIndexBuffer::IT_16BIT,
			getIndexBufferSize(info->type),
			HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		idata = (uint16*)info->buffer->lock(HardwareBuffer::HBL_DISCARD);

		for(unsigned int i = 0; i < rowlength - 1; i++)
		{
			for(unsigned int j = 0; j < rowlength - 1; j++)
			{
				// Face 1 of the quad
				*idata++ = i * rowlength + j;
				*idata++ = (i+1) * rowlength + j;
				*idata++ = i * rowlength + (j + 1);
				
				// Face 2 of the quad
				*idata++ = i * rowlength + (j + 1);
				*idata++ = (i+1) * rowlength + j;
				*idata++ = (i+1) * rowlength + (j+1);
			}
		}

		info->buffer->unlock();
		return info;
	}

	PlanetaryIndexBufferManager::INDEXBUFFERINFO *PlanetaryIndexBufferManager::createZoneToPageJoint()
	{
		uint16 *idata;
		INDEXBUFFERINFO *info = new INDEXBUFFERINFO;
		const unsigned int rowlength = mOptions->mZoneSizeLOD1;
		const unsigned int rowlength2 = (mOptions->mPageSizeLOD1 - 1) * mOptions->ZoneSize + 1;
		unsigned int trianglesPrZoneVert = Math::Ceil((rowlength2-1) / (rowlength-1));

		info->type = INDEX_BUFFER_ZONE_TO_PAGE_JOINT;
		info->LOD = 0;
		info->buffer = HardwareBufferManager::getSingleton().createIndexBuffer(
			HardwareIndexBuffer::IT_16BIT,
			getIndexBufferSize(info->type),
			HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		idata = (uint16*)info->buffer->lock(HardwareBuffer::HBL_DISCARD);

		unsigned int isize = getIndexBufferSize(info->type);
		
		unsigned int tmp;
		unsigned int lastv;

		for(unsigned int i = 0; i < rowlength; i++)
		{
			// i = parent vertex
			// j = child vertices connected to the parent vertex
			
			if(i == 0) // First parent vertex
			{
				for(unsigned int j = 0; j < (trianglesPrZoneVert / 2); j++)
				{
					*idata++ = i;
					tmp = i + rowlength + j;
					//tmp = i + (rowlength - i) + (rowlength - (rowlength - i)) * trianglesPrZoneVert;
					*idata++ = tmp;
					tmp++;
					//tmp = i + rowlength + ((i-1) * (trianglesPrZoneVert / 2)) + (i * trianglesPrZoneVert) + j + 1;
					*idata++ = tmp;
				}

				lastv = tmp;
			}
			else if(i == rowlength - 1) // Last parent vertex
			{
				for(unsigned int j = 0; j < (trianglesPrZoneVert / 2); j++)
				{
					*idata++ = i;
					tmp = i + rowlength2 - (trianglesPrZoneVert / 2) + j;
					*idata++ = tmp;
					tmp++;
					*idata++ = tmp;
				}

				// Add the "top" triangle
				*idata++ = i - 1;
				*idata++ = lastv;
				*idata++ = i;
			}
			else // Other vertices
			{
				for(unsigned int j = 0; j < trianglesPrZoneVert; j++)
				{					
					*idata++ = i;
					//tmp = i + (rowlength - i) + (rowlength - (rowlength - (i-1))) * trianglesPrZoneVert + (trianglesPrZoneVert / 2) + j - 1;
					tmp = rowlength + (trianglesPrZoneVert / 2) + ((i-1) * trianglesPrZoneVert) + j;
					*idata++ = tmp;
					//tmp = i + (rowlength - i) + (rowlength - (rowlength - (i-1))) * trianglesPrZoneVert + (trianglesPrZoneVert / 2) + j;
					tmp++;
					*idata++ = tmp;
				}

				// Add the "top" triangle
				*idata++ = i - 1;
				*idata++ = lastv;
				*idata++ = i;

				lastv = tmp;
			}
			
		}

		info->buffer->unlock();

		return info;
	}


	PlanetaryIndexBufferManager::INDEXBUFFERINFO *PlanetaryIndexBufferManager::createPageToTileJoint()
	{
		uint16 *idata;
		INDEXBUFFERINFO *info = new INDEXBUFFERINFO;
		const unsigned int rowlength = mOptions->mPageSizeLOD1;
		const unsigned int rowlength2 = (mOptions->TileSize - 1) * mOptions->PageSize + 1;
		unsigned int trianglesPrZoneVert = Math::Ceil((rowlength2-1) / (rowlength-1));

		info->type = INDEX_BUFFER_PAGE_TO_TILE_JOINT;
		info->LOD = 0;

		unsigned int isize = getIndexBufferSize(info->type);
		info->buffer = HardwareBufferManager::getSingleton().createIndexBuffer(
			HardwareIndexBuffer::IT_16BIT,
			isize,
			HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		idata = (uint16*)info->buffer->lock(HardwareBuffer::HBL_DISCARD);
		
		unsigned int tmp;
		unsigned int lastv;

		for(unsigned int i = 0; i < rowlength; i++)
		{
			// i = parent vertex
			// j = child vertices connected to the parent vertex
			
			if(i == 0) // First parent vertex
			{
				for(unsigned int j = 0; j < (trianglesPrZoneVert / 2); j++)
				{
					*idata++ = i;
					tmp = i + rowlength + j;
					//tmp = i + (rowlength - i) + (rowlength - (rowlength - i)) * trianglesPrZoneVert;
					*idata++ = tmp;
					tmp++;
					//tmp = i + rowlength + ((i-1) * (trianglesPrZoneVert / 2)) + (i * trianglesPrZoneVert) + j + 1;
					*idata++ = tmp;
				}

				lastv = tmp;
			}
			else if(i == rowlength - 1) // Last parent vertex
			{
				for(unsigned int j = 0; j < (trianglesPrZoneVert / 2); j++)
				{
					*idata++ = i;
					tmp = i + rowlength2 - (trianglesPrZoneVert / 2) + j;
					*idata++ = tmp;
					tmp++;
					*idata++ = tmp;
				}

				// Add the "top" triangle
				*idata++ = i - 1;
				*idata++ = lastv;
				*idata++ = i;
			}
			else // Other vertices
			{
				for(unsigned int j = 0; j < trianglesPrZoneVert; j++)
				{					
					*idata++ = i;
					//tmp = i + (rowlength - i) + (rowlength - (rowlength - (i-1))) * trianglesPrZoneVert + (trianglesPrZoneVert / 2) + j - 1;
					tmp = rowlength + (trianglesPrZoneVert / 2) + ((i-1) * trianglesPrZoneVert) + j;
					*idata++ = tmp;
					//tmp = i + (rowlength - i) + (rowlength - (rowlength - (i-1))) * trianglesPrZoneVert + (trianglesPrZoneVert / 2) + j;
					tmp++;
					*idata++ = tmp;
				}

				// Add the "top" triangle
				*idata++ = i - 1;
				*idata++ = lastv;
				*idata++ = i;

				lastv = tmp;
			}
			
		}

		info->buffer->unlock();

		return info;
	}
}
